/*
 * Copyright (c) 2005, Thomas J. Clancy
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of 
 * this software and associated documentation files (the "Software"), to deal in 
 * the Software without restriction, including without limitation the rights to use, 
 * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 
 * Software, and to permit persons to whom the Software is furnished to do so, 
 * subject to the following conditions:
 * 
 * 	1. 	The above copyright notice and this permission notice shall be included in 
 * 		all	copies or substantial portions of the Software. 
 * 
 * 	2.	Neither the name of the organization nor the names of its contributors may 
 * 		be used to endorse or promote products derived from this software without 
 * 		specific prior written permission.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *  
 */

package org.jcs.simulation.neds.gui;

import java.awt.BorderLayout;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;

import javax.swing.JPanel;

import org.jcs.simulation.neds.Coordinate;
import org.jcs.simulation.neds.Dimensions;
import org.jcs.simulation.neds.Ned;
import org.jcs.simulation.neds.Neds;
import org.jcs.simulation.neds.NedsEvent;
import org.jcs.simulation.neds.NedsEventListener;
import org.jcs.simulation.neds.Patch;

import com.sun.j3d.utils.universe.*;
import com.sun.j3d.utils.geometry.*;
import java.util.Random;
import javax.media.j3d.*;
import javax.vecmath.*;

public class NedField3D extends JPanel implements NedsEventListener, ComponentListener {
    private static Logger log = Logger.getLogger(NedField3D.class.getName());
    private static final long serialVersionUID = -1931472112999786156L;
    private static final ReentrantLock lock = new ReentrantLock();
    private GuiPatch3D[][] guiPatches3D = null;
    private Patch[][] patches = null;
    private int rows;
    private int cols;
    public GuiPatch3D[][] getGuiPatches3D() {
		return guiPatches3D;
	}

	private boolean initialized = false;
    private Dimensions dimensions;
    private Neds neds;
    private NedEngine3D engine;
    private BranchGroup nedGroup;

    public NedField3D(Neds neds) {
        super(true);
        this.neds = neds;
        this.dimensions = neds.getDimension();
        this.engine = new NedEngine3D(dimensions);
        this.neds = neds;
        this.patches = neds.getFields();
        this.setLayout(new BorderLayout());
        this.neds.addNedsEventListener(this);
        this.add(engine);
        addComponentListener(this);
    }
    
    public Dimensions getDimension() {
        return dimensions;
    }

    public void paintComponent(Graphics g) {
        lock.lock();
        try {
            if (!initialized) {
                initialize();
                engine.run();
            }
            paintPatches();
            paintNedsRandomly();
        } finally {
            lock.unlock();
        }
    }

    private void paintPatches() {
        for (int y = 0; y < cols; y++) {
            for (int x = 0; x < rows; x++) {
                GuiPatch3D p = guiPatches3D[x][y];
                p.paint();
            }
        }
    }

    private void paintNedsRandomly() {
        Set<Ned> ns = neds.getAllNeds();
        if(nedGroup != null)
        	nedGroup.detach();
        BranchGroup newNedGroup = new BranchGroup();
        for (Ned ned : ns) {
            GuiPatch3D gp = null;
            Coordinate c = null;
            Sphere ned3D;
            if (ned.getPoint() == null) {
                gp = getGuiPatch3D(ned.getCoord());
                c = Coordinate.newRandomCoordinate(gp.getUpperLeft(), gp.getSize());
                ned.setPoint(new Point(c.getX(), c.getY()));
            }
            else {
                c = new Coordinate(ned.getPoint().x, ned.getPoint().y);
            }
            Transform3D tf = new Transform3D();
            TransformGroup tg = new TransformGroup();
            //Vector3f pos = new Vector3f(-1.0f+((2.0f)*c.getX())/this.getWidth(),.5f-((1.0f)*c.getY())/this.getHeight(), 0.0f);

			// Hack to fix the neds positions.
			float x_unit = 2.1f/8.0f;
			float y_unit = 1.1f/8.0f;
			int x_pos = ned.getCoord().getX();
			int y_pos = ned.getCoord().getY();
			float x_start = (-0.5f*(8.0f))*x_unit+x_unit/2;
			float y_start = (0.5f*(8.0f))*y_unit-y_unit/2;

			Random r = new Random();
			r.nextFloat();

			//Vector3f pos = new Vector3f(1.0f*c.getX()/this.getWidth(),1.0f*c.getY()/this.getHeight(), 0.0f);
			Vector3f pos = new Vector3f(
				x_start+x_pos*x_unit + (r.nextFloat()*1.0f/8.0f),
				y_start-y_pos*y_unit + (r.nextFloat()*1.0f/16.0f),
				0.0f);

            tf.setTranslation(pos);
            tg.setTransform(tf);
            Appearance appearance = new Appearance();
            Material material = new Material();
            material.setEmissiveColor(0.0f, 0.0f, 0.0f);
            appearance.setMaterial(material);
            if (ned.getSex() == 'm') {
            	material.setEmissiveColor(0.0f, 0.0f, .8f);
                appearance.setMaterial(material);
            } else {
            	material.setEmissiveColor(0.6f, 0.0f, 0.0f);
                appearance.setMaterial(material);
            }
            if (ned.getAge() < 12) {
                ned3D = new Sphere(.015f, appearance);
            }
            else {
                ned3D = new Sphere(.035f, appearance);
            }
            tg.addChild(ned3D);
            newNedGroup.addChild(tg);
        }
        engine.addBranch(newNedGroup);
        nedGroup = newNedGroup;
    }

    private GuiPatch3D getGuiPatch3D(Coordinate c) {
        return guiPatches3D[c.getX()][c.getY()];
    }
    
    /**
     * This method initializes this
     * 
     * @return void
     */
    public void initialize() {
        log.info("initialize: " + dimensions);
        this.patches = neds.getFields();
        
        int w = this.getWidth();
        int h = this.getHeight();

        rows = dimensions.getHeight();
        cols = dimensions.getWidth();

        int patchWidth = w / cols;
        int patchHeight = h / rows;

        Dimensions patchDimensions = new Dimensions(patchWidth, patchHeight);
        
        guiPatches3D = new GuiPatch3D[cols][rows];

        neds.resetNedPoint();
        
        lock.lock();
        try {
            int top = 0;
            for (int y = 0; y < rows; y++) {
                int left = 0;
                for (int x = 0; x < cols; x++) {
                	
                    Coordinate c = new Coordinate(left, top);
                    GuiPatch3D gp = new GuiPatch3D(patches[x][y], c, patchDimensions);
                    guiPatches3D[x][y] = gp;
                    engine.addPatch(guiPatches3D[x][y].getMaterial());
                    left += patchWidth;
                }
                top += patchHeight;
            }
            setVisible(true);
            initialized = true;
        } finally {
            lock.unlock();
        }
    }

    public NedEngine3D getEngine() {
		return engine;
	}

	public void handle(NedsEvent event) {
        if (event.getEvent().equals("step")) {
            repaint(0);
        }
        
    }
    
    public void cameraMove(int x, int moveSize)
    {
    	engine.cameraUpdate(x, moveSize);
    }

    public void componentHidden(ComponentEvent e) {
    }

    public void componentMoved(ComponentEvent e) {
    }

    public void componentResized(ComponentEvent e) {
    }

    public void componentShown(ComponentEvent e) {
    }

}
